﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Drifting
{
    public class Camera
    {
        private GraphicsDevice device;
        private float cameraX;
        private float cameraY;
        private float cameraZ;
        private float cameraLookAtX;
        private float cameraLookAtY;
        private float cameraLookAtZ;
        private float viewAngle = MathHelper.Pi / 15;

        private double pitch_delta;
        private double yaw_delta;

        private Vector3 InitCameraVector;
        private Vector3 InitCameraLookAt;
        private float InitViewAngle;

        private bool isReady;

        public void CameraRefresh(out Matrix viewMatrix, out Matrix projectionMatrix)
        {
            viewMatrix = Matrix.CreateLookAt(
                new Vector3(cameraX, cameraY, cameraZ),
                new Vector3(cameraLookAtX, cameraLookAtY, cameraLookAtZ),
                new Vector3(0, 1, 0));

            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(
                viewAngle,
                device.Viewport.AspectRatio,
                0.1f,
                1000.0f);
        }

        public Camera(GraphicsDevice device)
        {
            cameraX = 1;
            cameraY = 1;
            cameraZ = 1;
            cameraLookAtX = 0;
            cameraLookAtY = 0;
            cameraLookAtZ = 0;

            this.device = device;
            
            pitch_delta = 0.001;
            yaw_delta = 0.005;
            isReady = false;
        }

        public void Camera_InitView(float x, float y, float z, float x_lookat, float y_lookat, float z_lookat, float viewAngle, ref Matrix viewMatrix, ref Matrix projectionMatrix)
        {
            InitCameraVector = new Vector3(x, y, z);
            InitCameraLookAt = new Vector3(x_lookat, y_lookat, z_lookat);
            InitViewAngle = viewAngle;
            this.viewAngle = viewAngle;
            
            ResetViewParameters();
            CameraRefresh(out viewMatrix, out projectionMatrix);

            isReady = true;
        }

        public void ResetViewParameters()
        {
            cameraX = InitCameraVector.X;
            cameraY = InitCameraVector.Y;
            cameraZ = InitCameraVector.Z;
            cameraLookAtX = InitCameraLookAt.X;
            cameraLookAtY = InitCameraLookAt.Y;
            cameraLookAtZ = InitCameraLookAt.Z;
            viewAngle = InitViewAngle;
        }

        public bool IsReady
        {
            get{ return isReady; }
        }

        public void Update(double camera_x, double camera_y, double camera_z, ref Matrix viewMatrix, ref Matrix projectionMatrix)
        {
            cameraX = (float)camera_x;
            cameraY = (float)camera_y;
            cameraZ = (float)camera_z;
            CameraRefresh(out viewMatrix, out projectionMatrix);
        }

        public void Update(ref Matrix viewMatrix, ref Matrix projectionMatrix)
        {
            if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.Up))
            {
                double dx = cameraX - cameraLookAtX;
                double dy = cameraY - cameraLookAtY;
                double dz = cameraZ - cameraLookAtZ;

                double yaw = Math.Atan2(dz, dx);
                double yaw_length = Math.Sqrt(dx * dx + dz * dz);

                double pitch = Math.Atan2(dy, yaw_length);
                double pitch_length = Math.Sqrt(dx * dx + dy * dy + dz * dz);
                float xz = (float)(Math.Cos(pitch) * pitch_length);

                if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.LeftShift))
                {
                    pitch -= pitch_delta;

                    cameraLookAtY = cameraY - (float)(Math.Sin(pitch) * pitch_length);
                    cameraLookAtX = cameraX - (float)(Math.Cos(yaw) * xz);
                    cameraLookAtZ = cameraZ - (float)(Math.Sin(yaw) * xz);
                }
                else
                {
                    pitch += pitch_delta;

                    cameraY = cameraLookAtY + (float)(Math.Sin(pitch) * pitch_length);
                    cameraX = cameraLookAtX + (float)(Math.Cos(yaw) * xz);
                    cameraZ = cameraLookAtZ + (float)(Math.Sin(yaw) * xz);
                }

                CameraRefresh(out viewMatrix, out projectionMatrix);
            }
            if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.Down))
            {
                double dx = cameraX - cameraLookAtX;
                double dy = cameraY - cameraLookAtY;
                double dz = cameraZ - cameraLookAtZ;

                double yaw = Math.Atan2(dz, dx);
                double yaw_length = Math.Sqrt(dx * dx + dz * dz);

                double pitch = Math.Atan2(dy, yaw_length);
                double pitch_length = Math.Sqrt(dx * dx + dy * dy + dz * dz);
                float xz = (float)(Math.Cos(pitch) * pitch_length);

                if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.LeftShift))
                {
                    pitch += pitch_delta;

                    cameraLookAtY = cameraY - (float)(Math.Sin(pitch) * pitch_length);
                    cameraLookAtX = cameraX - (float)(Math.Cos(yaw) * xz);
                    cameraLookAtZ = cameraZ - (float)(Math.Sin(yaw) * xz);
                }
                else
                {
                    pitch -= pitch_delta;

                    cameraY = cameraLookAtY + (float)(Math.Sin(pitch) * pitch_length);
                    cameraX = cameraLookAtX + (float)(Math.Cos(yaw) * xz);
                    cameraZ = cameraLookAtZ + (float)(Math.Sin(yaw) * xz);
                }

                CameraRefresh(out viewMatrix, out projectionMatrix);
            }
            if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.Left))
            {
                double dx = cameraX - cameraLookAtX;
                double dy = cameraY - cameraLookAtY;
                double dz = cameraZ - cameraLookAtZ;

                double yaw = Math.Atan2(dz, dx);
                double yaw_length = Math.Sqrt(dx * dx + dz * dz);

                if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.LeftShift))
                {
                    yaw -= yaw_delta;
                    cameraLookAtX = cameraX - (float)(Math.Cos(yaw) * yaw_length);
                    cameraLookAtZ = cameraZ - (float)(Math.Sin(yaw) * yaw_length);
                }
                else
                {
                    yaw += yaw_delta;
                    cameraX = cameraLookAtX + (float)(Math.Cos(yaw) * yaw_length);
                    cameraZ = cameraLookAtZ + (float)(Math.Sin(yaw) * yaw_length);
                }

                CameraRefresh(out viewMatrix, out projectionMatrix);
            }
            if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.Right))
            {
                double dx = cameraX - cameraLookAtX;
                double dy = cameraY - cameraLookAtY;
                double dz = cameraZ - cameraLookAtZ;

                double yaw = Math.Atan2(dz, dx);
                double yaw_length = Math.Sqrt(dx * dx + dz * dz);

                if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.LeftShift))
                {
                    yaw += yaw_delta;
                    cameraLookAtX = cameraX - (float)(Math.Cos(yaw) * yaw_length);
                    cameraLookAtZ = cameraZ - (float)(Math.Sin(yaw) * yaw_length);
                }
                else
                {
                    yaw -= yaw_delta;
                    cameraX = cameraLookAtX + (float)(Math.Cos(yaw) * yaw_length);
                    cameraZ = cameraLookAtZ + (float)(Math.Sin(yaw) * yaw_length);
                }

                CameraRefresh(out viewMatrix, out projectionMatrix);
            }


            if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.M))
            {
                viewAngle += 0.01f;
                if (viewAngle > Math.PI / 2)
                    viewAngle = 1.57f;
                CameraRefresh(out viewMatrix, out projectionMatrix);
            }
            if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.N))
            {
                viewAngle -= 0.01f;
                if (viewAngle < 0.01)
                    viewAngle = 0.01f;
                CameraRefresh(out viewMatrix, out projectionMatrix);
            }

            if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.H))
            {
                double dx = cameraX - cameraLookAtX;
                double dy = cameraY - cameraLookAtY;
                double dz = cameraZ - cameraLookAtZ;
                double length = Math.Sqrt(dx * dx + dy * dy + dz * dz);
                if (length < 0) return;
                if (length > 500) return;
                double length_new = length - 0.5f;
                double t = length_new / length;
                cameraX = (float)(cameraLookAtX + dx * t);
                cameraY = (float)(cameraLookAtY + dy * t);
                cameraZ = (float)(cameraLookAtZ + dz * t);

                CameraRefresh(out viewMatrix, out projectionMatrix);
            }

            if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.J))
            {
                double dx = cameraX - cameraLookAtX;
                double dy = cameraY - cameraLookAtY;
                double dz = cameraZ - cameraLookAtZ;
                double length = Math.Sqrt(dx * dx + dy * dy + dz * dz);
                if (length < 0) return;
                if (length > 500) return;
                double length_new = length + 0.5f;
                double t = length_new / length;
                cameraX = (float)(cameraLookAtX + dx * t);
                cameraY = (float)(cameraLookAtY + dy * t);
                cameraZ = (float)(cameraLookAtZ + dz * t);

                CameraRefresh(out viewMatrix, out projectionMatrix);
            }
        }

    }
}
